feat: add the meshStack CLI and the API client it shares with the Terraform provider - #2
Draft
grubmeshi wants to merge 212 commits into
Draft
feat: add the meshStack CLI and the API client it shares with the Terraform provider#2grubmeshi wants to merge 212 commits into
grubmeshi wants to merge 212 commits into
Conversation
meshStack enforces the Accept header soon, so we have to make sure to always provide it
includes adaptations from PR remarks
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e_flags Replaces the is_four_eyes_enabled bool with a more general enabled_feature_flags set-of-strings attribute (currently only four_eyes_role_approval), per PR review.
…grations New spec.config.entraid.idp_alias adopts an identity provider that already exists in a meshStack instance, instead of always creating one. Optional and Computed, so an existing integration plans no change. The alias is immutable. meshStack deletes the identity provider when the integration is deleted, so expressing a change as a destroy and recreate would delete a provider the customer may have configured themselves. A plan modifier rejects the change instead, as version_spec does in building_block_definition_resource.go, and rejects a change it cannot verify when state carries no alias to compare against. The framework ships nothing equivalent: stringplanmodifier has only RequiresReplace variants and UseStateForUnknown, and a validator cannot see prior state.
meshStack derives the redirect URL and returns it inside spec, which configuration writes. A computed value there is unreachable under provider mocks and cannot be supplied by override_* either, so any module wiring that reads it is untestable (#272). It now lives at status.entraid.redirect_url, a fully computed container, nested per integration type the way status.workload_identity_federation nests per cloud. That also retires a trap: the attribute was Optional as well as Computed, but meshStack ignores a supplied value, so a configuration that set it failed the apply with "Provider produced inconsistent result after apply" — after the integration had been created. Derived through a local model struct rather than a json:"-" field on the client type, per the computed-only output field pattern in the resource-development skill. integrationStatus is shared by the resource and the data source.
A building block definition can now carry a display_name_template, so meshStack names each ordered building block after the values it was ordered with while the definition keeps a clean display_name. Without the attribute a building block is named after display_name, and an empty string means the same thing. meshStack has to serve the field: an older one drops it from its response, so an apply that sets it fails Terraform's consistency check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The meshBuildingBlockDefinition meshObject API now carries the meshPanel Policies tab in spec.approvalPolicies and spec.schedule. They default to no approval gate and no schedule, which is what meshStack stores for a new definition. meshStack validates both against the implementation type of the definition's latest version, and it rejects a version implementation-type change while the stored policies are incompatible with the new type. So a policy the current type cannot honour has to be written after version_spec, and one the new type cannot honour has to be gone before it. Neutral policies satisfy every implementation type, so Create writes the definition neutral, writes the version, then writes the planned policies, and Update passes through neutral in between when the implementation type changes. That makes a combined implementation-type and policy change apply in one step. Two object validators repeat meshStack's rules at plan time, so an unsupported combination fails before anything is written. The mock client learns the same rules, so a unit-test run catches an ordering regression without a live backend. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
meshStack lets a Building Block Definition input name a tag instead of asking users to re-type metadata meshStack already governs. Declaring one takes the new TAG assignment type, a CODE input type and an argument holding the `<target>.<tagKey>` reference, so the only code change is the enum entry that the assignment_type validator builds its allowed set from. The rules that come with it - which targets a definition may read, and why the input type is fixed - are documented on the two attributes that carry them, and the terraform example shows the argument referencing a meshstack_tag_definition rather than a literal key, which also gets the destroy order right. The meshTagDefinition delete now refuses while a building block reads the tag, so the tag definition resource says so too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An operator can now mark a definition input optional via version_spec.inputs.*.is_optional, so whoever fills the Building Block in may leave it blank and the implementation falls back to the default declared in its own code — a Terraform variable's default, a workflow input's default. Mirrors meshcloud/meshfed-release#10701. isOptional is sent with omitempty. false is the backend's default for an absent field, so a non-optional input serialises exactly as it did before the field existed: every already-stored content hash stays byte-identical (no currentHashVersion bump, so no released Building Block re-runs), and a backend that does not know the field still sees the same payload for configs that do not use the feature. A new fixture pins that equality so a later change cannot silently break it. MinMeshStackVersion deliberately stays put. Unlike a field the provider always sends, this one only reaches the backend for someone who opted in, so gating every user of the provider would cost more than it protects. ValidateConfig re-imposes the backend's four rules at plan time rather than letting them surface as a 400 during apply: no optional input on a MANUAL implementation (a person carries the block out, so there is no code to fall back to), only for the assignment types a person supplies, never for BOOLEAN (an unset boolean is indistinguishable from false where it is consumed), and never together with a meshStack default value. Checks whose value is unknown at plan are skipped, keeping the backend the authority. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two halves belong in one commit because the linter refuses to run when they are split: a golangci-lint built by go 1.26 rejects a module targeting 1.27 with "the Go language version (go1.26) used to build golangci-lint is lower than the targeted Go version (1.27)". Lint therefore runs differently now. golangci-lint's formatters use the go/format compiled into the binary, so the formatting they enforce comes from the Go release that BUILT the linter rather than the toolchain on PATH. Its version was pinned in two places that could drift - flake.nix took whatever nixpkgs packaged, CI asked for latest - and neither was tied to go.mod's Go. It joins tfplugindocs and gotestsum in the tool block instead, so go.mod and go.sum are the single pin and `task lint` runs `go tool golangci-lint run`, which builds it with the same Go the code is written against. That also works outside `nix develop`, which it did not before, so flake.nix no longer carries the package - one pin fewer to keep in step. CI keeps golangci-lint-action, because it annotates the pull request diff and a bare `run:` does not. It installs nothing though: the step before it runs `go install` for the pinned package and the action uses install-mode none, so the version it runs is the one in go.mod. The job also moves off `go-version: stable` onto go.mod's Go, for the same reason. Rebuilding the linter with go 1.27 changed one alignment group in client/internal/retry_test.go, which is the behaviour described above showing up in practice rather than an unrelated edit. The changelog entry opens a new v0.25.3 section. v0.25.2 is already tagged, so per the changelog-management skill a pending entry needs a new top section rather than an amendment to a released one. Two knock-on changes: - flake.lock had to move forward, because the June nixpkgs it pinned carries no go_1_27 attribute and `nix develop` failed outright with "undefined variable 'go_1_27'". - testify goes 1.11.1 -> 1.12.1, raised by MVS because golangci-lint requires at least that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A definition input can now declare type JSON_SCHEMA together with the schema its value follows, matching the new backend input type. meshPanel renders a form from the schema, and the value reaches the building block as JSON text, exactly like a CODE input. JSON_SCHEMA is deliberately not an entry of MeshBuildingBlockIOTypes: that enum describes the type of a value, and a building block's own inputs keep reporting CODE for these. Only the definition input schema offers it, via MeshBuildingBlockDefinitionInputTypes. ValidateConfig re-imposes the pairing the framework cannot express — json_schema is required for the type and rejected for every other — so a mismatch fails at plan time rather than as a 400. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The list of types a definition input may declare is the full IO type set plus JSON_SCHEMA, so spell it that way instead of repeating all nine entries. A new Enum.With copies via slices.Concat rather than appending, so the package-level MeshBuildingBlockIOTypes can never be written into through its shared backing array. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wording led with the value the input produces, which reads as "CODE with a schema attached" and hides what the type actually does: it gives the input a form of its own, declared by json_schema, that meshPanel renders in place of a single field. Lead with the form everywhere the type is described — the `type` attribute, `json_schema`, the ValidateConfig messages, the example and the changelog — and keep the CODE equivalence as the follow-up it is, since that is only true of what the form produces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CU-86cb59bn3
Follows the backend rename: the type names the value the input holds, and the schema stays a separate argument on it. The type ships first in meshStack 2026.37.0, which the changelog entry now states. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The client was the Terraform provider's own package. The CLI needs the same calls, and a public repository is the only place both can depend on it, so the client's home is here now and the provider consumes it as a module. This is a merge rather than a copy so that the client keeps the history it has in the provider: `git log` and `git blame` on these files still reach every change made there, and `git subtree` can still move a change either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> git-subtree-dir: client git-subtree-mainline: f2ba137 git-subtree-split: cf78ea1
grubmeshi
force-pushed
the
feature/scaffold-cli
branch
2 times, most recently
from
September 9, 2026 18:44
d2fe4b5 to
843633e
Compare
The client's imports name this module now, and the HTTP machinery it was built on sits at the module root as internal/http rather than under client/internal: pkg/oidc and pkg/auth need the same client, and Go's internal rule closes client/internal to both. internal/http is tested from the outside, through the types its callers parse answers into, so its test reaches for pkg/oidc/jwt and pulls pkg/meshstack and pkg/oidc/scope along. Those three land here instead of with the rest of the CLI so that this commit's own suite runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
meshstack authenticates against meshStack — a browser login, an API key, an API token, or the local dev stack's own published credentials — keeps the result in a named profile, and lists the workspaces that credential reaches. Every setting is declared once beside the domain it belongs to, so the CLI's flags and the Terraform provider's block resolve the same thing the same way. The plumbing that ships it comes along: goreleaser builds the archives, a container image goes to GHCR, and the dependency policy that keeps the provider's dependency tree small sits in the linter configuration, where widening it is a deliberate edit rather than a lint fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
grubmeshi
force-pushed
the
feature/scaffold-cli
branch
from
September 9, 2026 19:22
843633e to
394e45e
Compare
Collaborator
Author
|
Superseded by the stack #3 (the satellite hull) and #4 (the client subtree and the CLI), which rebuild this branch on top of a registered satellite. Closing it also matters for CI: meshfed-release's |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Imports
client/from the Terraform provider as a git subtree, split at the provider'sv0.25.3, so both tools share one client instead of each carrying its own. The CLI itself follows on top.Stacked on #3. Paired by branch name with meshcloud/meshfed-release#10866, whose credentials the acceptance suite bootstraps from, and with meshcloud/terraform-provider-meshstack#299, which consumes this.
ClickUp: 86cb61rzz, milestone 86cb61we4.